﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Controls.Primitives;
using System.Diagnostics;
using System.Windows.Media;
using System.Windows.Markup;

namespace Png.Silverlight.Controls
{
    /// <summary>
    /// AutoExpandOver
    /// </summary>
    [TemplatePart(Name = AutoExpandOver.RootElementPart, Type = typeof(Panel))]
    [TemplatePart(Name = AutoExpandOver.HeaderElementPart, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = AutoExpandOver.ContentElementPart, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = AutoExpandOver.PopupElementPart, Type = typeof(Popup))]
    [TemplateVisualState(Name = CollapsedState, GroupName = CommonStateGroup)]
    [TemplateVisualState(Name = ExpandedState, GroupName = CommonStateGroup)]
    public class AutoExpandOver : ContentControl
    {

    #region Template Constants
        private const string RootElementPart = "RootElement1";
        private const string HeaderElementPart = "HeaderElement1";
        private const string ContentElementPart = "ContentElement1";
        private const string PopupElementPart = "PopupElement1";

        private const string CommonStateGroup = "CommonStates";

        private const string CollapsedState = "Collapsed";
        private const string ExpandedState = "Expanded";
    #endregion

    #region Private Properties
        private bool _firstFocus = true;
        private bool _hasFocus = false;
        private bool _isMouseOver = false;
        private System.Threading.Timer _popTimer; 
        private Panel _rootElement;
        private FrameworkElement _headerElement;
        private FrameworkElement _contentElement;
        private Popup _popupElement;

        private Canvas OutsidePopupCanvas { get; set; }
        private Canvas PopupChildCanvas { get; set; }
        private FrameworkElement PopupChild { get; set; }
        private Panel RootElement
        {
            get
            {
                return _rootElement;
            }
            set
            {
                if (_rootElement != null)
                {
                    //remove existing event handler
                    //DetachEvents(_rootElement);
                }

                _rootElement = value;
                if (_rootElement != null)
                {
                    //add event handler
                    //AttachEvents(_rootElement);
                }
            }
        }
        private FrameworkElement HeaderElement
        {
            get
            {
                return _headerElement;
            }
            set
            {
                if (_headerElement != null)
                {
                    //remove existing event handler
                    DetachEvents(_headerElement);
                }

                _headerElement = value;
                if (_headerElement != null)
                {
                    //add event handler
                    AttachEvents(_headerElement);
                }
            }
        }
        private FrameworkElement ContentElement
        {
            get
            {
                return _contentElement;
            }
            set
            {
                if (_contentElement != null)
                {
                    //remove existing event handler
                    DetachEvents(_contentElement);
                }

                _contentElement = value;
                if (_contentElement != null)
                {
                    //add event handler
                    AttachEvents(_contentElement);
                }
            }
        }
        private Popup PopupElement
        {
            get
            {
                return _popupElement;
            }
            set
            {
                if (_popupElement != null)
                {
                    //remove existing event handler
                    //DetachEvents(_popupElement);
                }

                _popupElement = value;
                if (_popupElement != null)
                {
                    //add event handler
                    //AttachEvents(_popupElement);
                }
            }
        }
        private bool HasFocus
        {
            get
            {
                return _hasFocus;
            }
            set
            {
                if (_hasFocus != value)
                {
                    _hasFocus = value;
                    if (_hasFocus)
                    {
                        if (_openOnFocus)
                        {
                            OpenPopup(false);
                        }
                        FirstFocus = !_hasFocus;
                    }
                }
                _hasFocus = value;
                if (!_hasFocus)
                {
                    FirstFocus = true;
                    //if (_closeOnHover)
                    //{
                    //    ClosePopup(false);
                    //}
                }
            }
        }
        private bool FirstFocus
        {
            get
            {
                return _firstFocus;
            }
            set
            {
                //if firstFocus is true and we are setting it to false, set active focus to the textbox
                if (_firstFocus & !value)
                {
                    //Text1.Focus();
                    //Text1.SelectAll();
                }
                _firstFocus = value;

            }
        }
        private bool IsMouseOver
        {
            get
            {
                if (_openOnHover)
                {
                    return _isMouseOver;
                }
                else
                {
                    return false;
                }
            }
            set { _isMouseOver = value; }
        }        
    #endregion
        
    #region Public Properties
        #region HeaderProperty
        public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(Object), typeof(AutoExpandOver), null);
        public Object Header
        {
            get
            {
                return (Object)GetValue(HeaderProperty);
            }

            set
            {
                SetValue(HeaderProperty, value);
            }
        }
        #endregion

        #region HeaderTemplateProperty
        public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(AutoExpandOver), null);
        public DataTemplate HeaderTemplate { get; set; }
        #endregion

        #region ContentProperty
        //ContentProperty is inherited through ContentControl
        #endregion

        #region OpenDelayProperty
        public static readonly DependencyProperty OpenDelayProperty = DependencyProperty.Register("OpenDelay", typeof(int), typeof(AutoExpandOver), null);
        public int OpenDelay
        {
            get
            {
                return (int)GetValue(OpenDelayProperty);
            }

            set
            {
                SetValue(OpenDelayProperty, value);
            }
        }
        #endregion

        #region ValidateChildGridProperty
        public static readonly DependencyProperty ValidateChildGridProperty = DependencyProperty.Register("ValidateChildGrid", typeof(bool), typeof(AutoExpandOver), null);
        public bool ValidateChildGrid
        {
            get
            {
                return (bool)GetValue(ValidateChildGridProperty);
            }
            set
            {
                SetValue(ValidateChildGridProperty, value);
            }
        }
        #endregion

        #region MakeModalProperty
        public static readonly DependencyProperty MakeModalProperty = DependencyProperty.Register("MakeModal", typeof(bool), typeof(AutoExpandOver), null);
        /// <summary>
        /// Work in progress, buggy. If set to false you cannot be certain it will close properly.
        /// </summary>
        private bool _makeModal;
        public bool MakeModal
        {
            get
            {
                return (bool)GetValue(MakeModalProperty);
            }

            set
            {
                SetValue(MakeModalProperty, value);
                _makeModal = value;
            }
        }
        #endregion

        #region PopupEnabledProperty
        public static readonly DependencyProperty PopupEnabledProperty = DependencyProperty.Register("PopupEnabled", typeof(bool), typeof(AutoExpandOver), null);
        public bool PopupEnabled
        {
            get
            {
                return (bool)GetValue(PopupEnabledProperty);
            }

            set
            {
                SetValue(PopupEnabledProperty, value);
            }
        }
        #endregion

        #region CloseDelayProperty
        public static readonly DependencyProperty CloseDelayProperty = DependencyProperty.Register("CloseDelay", typeof(int), typeof(AutoExpandOver), null);
        public int CloseDelay
        {
            get
            {
                return (int)GetValue(CloseDelayProperty);
            }

            set
            {
                SetValue(CloseDelayProperty, value);
            }
        }
        #endregion

        #region OpenOnClickProperty
        public static readonly DependencyProperty OpenOnClickProperty = DependencyProperty.Register("OpenOnClick", typeof(bool), typeof(AutoExpandOver), null);
        /// <summary>
        /// When True, if you click on the header it will open the popup.
        /// </summary>
        public bool OpenOnClick
        {
            get
            {
                return (bool)GetValue(OpenOnClickProperty);
            }

            set
            {
                SetValue(OpenOnClickProperty, value);
            }
        }
        #endregion

        #region CloseOnClickProperty
        public static readonly DependencyProperty CloseOnClickProperty = DependencyProperty.Register("CloseOnClick", typeof(bool), typeof(AutoExpandOver), null);
        /// <summary>
        /// When True, if you click outside of the popup it will close. If False it will not close.
        /// </summary>
        public bool CloseOnClick
        {
            get
            {
                return (bool)GetValue(CloseOnClickProperty);
            }

            set
            {
                SetValue(CloseOnClickProperty, value);
            }
        }
        #endregion

        #region OpenOnFocusProperty
        private bool _openOnFocus = true;
        public static readonly DependencyProperty OpenOnFocusProperty = DependencyProperty.Register("OpenOnFocus", typeof(bool), typeof(AutoExpandOver), null);
        /// <summary>
        /// When True, if the control recieves focus it will open the popup. This can happen through tabbing, clicking, or code behind.
        /// </summary>
        public bool OpenOnFocus
        {
            get
            {
                return (bool)GetValue(OpenOnFocusProperty);
            }

            set
            {
                SetValue(OpenOnFocusProperty, value);
                _openOnFocus = value;
            }
        }
        #endregion

        #region OpenOnHoverProperty
        private bool _openOnHover = true;
        public static readonly DependencyProperty OpenOnHoverProperty = DependencyProperty.Register("OpenOnHover", typeof(bool), typeof(AutoExpandOver), null);
        /// <summary>
        /// When True, using OpenDelay it will open the popup when the mouse is over the header.
        /// </summary>
        public bool OpenOnHover
        {
            get
            {
                return (bool)GetValue(OpenOnHoverProperty);
            }

            set
            {
                SetValue(OpenOnHoverProperty, value);
                _openOnHover = value;
            }
        }
        #endregion

        #region CloseOnHoverProperty
        private bool _closeOnHover = true;
        public static readonly DependencyProperty CloseOnHoverProperty = DependencyProperty.Register("CloseOnHover", typeof(bool), typeof(AutoExpandOver), null);
        /// <summary>
        /// When True, using CloseDelay it will close the popup when the mouse is no longer over the popup.
        /// </summary>
        public bool CloseOnHover
        {
            get
            {
                return (bool)GetValue(CloseOnHoverProperty);
            }

            set
            {
                SetValue(CloseOnHoverProperty, value);
                _closeOnHover = value;
            }
        }
        #endregion

        #region HoldOpenWhenFocusedProperty
        private bool _holdOpenWhenFocused = true;
        public static readonly DependencyProperty HoldOpenWhenFocusedProperty = DependencyProperty.Register("HoldOpenWhenFocused", typeof(bool), typeof(AutoExpandOver), null);
        /// <summary>
        /// When True and when CloseOnHover=True, if a control inside the popup has focus, popup will not close when you hover off.
        /// </summary>
        public bool HoldOpenWhenFocused
        {
            get
            {
                return (bool)GetValue(HoldOpenWhenFocusedProperty);
            }

            set
            {
                SetValue(HoldOpenWhenFocusedProperty, value);
                _holdOpenWhenFocused = value;
            }
        }
        #endregion
    #endregion

    #region Events
        
        public event EventHandler PopupClosing;
        public event EventHandler PopupClosed;
        public event EventHandler PopupOpening;
        public event EventHandler PopupOpened;

        private void OnPopupClosing()
        {
            if (PopupClosing != null) {
                PopupClosing(this, new EventArgs());
            }
        }

        private void OnPopupClosed()
        {
            if (PopupClosed != null)
            {
                PopupClosed(this, new EventArgs());
            }
        }
 
        private void OnPopupOpening()
        {
            if (PopupOpening != null)
            {
                PopupOpening(this, new EventArgs());
            }
        }

        private void OnPopupOpened()
        {
            if (PopupOpened != null)
            {
                PopupOpened(this, new EventArgs());
            }
        }

    #endregion

    #region Private Methods

        private void DetachEvents(FrameworkElement element)
        {
            if (_openOnHover)
            {
                element.MouseEnter -= new MouseEventHandler(root_MouseEnter);
                element.MouseLeave -= new MouseEventHandler(root_MouseLeave);
            }
            if (_openOnFocus)
            {
                element.GotFocus -= new RoutedEventHandler(root_GotFocus);
                element.LostFocus -= new RoutedEventHandler(root_LostFocus);
            }
        }

        private void AttachEvents(FrameworkElement element)
        {
            if (_openOnHover)
            {
                element.MouseEnter += new MouseEventHandler(root_MouseEnter);
                element.MouseLeave += new MouseEventHandler(root_MouseLeave);
            }
            if (_openOnFocus)
            {
                element.GotFocus += new RoutedEventHandler(root_GotFocus);
                element.LostFocus += new RoutedEventHandler(root_LostFocus);
            }
        }
        private void AutoExpandOver_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (OpenOnClick)
            {
                OpenPopup(false);
            }
        }
        protected bool ChildIsValid(DependencyObject myVisual)
        {
            //TO DO: cache the grids to improve performance
            bool result = true;
            if (ValidateChildGrid)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(myVisual); i++)
                {
                    var childVisual = VisualTreeHelper.GetChild(myVisual, i);

                    var t = childVisual as DataGrid;
                    if (t != null)
                    {
                        t.BeginEdit();//if you don't edit before commit uou can miss validation
                        t.CommitEdit();
                        if (!t.IsValid)
                        {
                            result = false;
                        }
                    }

                    if (!ChildIsValid((DependencyObject)childVisual))
                    {
                        result = false;
                    }
                }
            }
            return result;
        }

        private void OutsidePopupCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (CloseOnClick)
            {
                if (ChildIsValid(PopupChild))
                {
                    ClosePopup(false);
                }
            }
        }
        private void ContentElement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            Debug.WriteLine("shut down click event");
        }

        private void root_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            IsMouseOver = true;
            if (_openOnHover)
            {
                _popTimer.Change(OpenDelay, System.Threading.Timeout.Infinite);
            }
            Debug.WriteLine("Mouse Enter={0}, {1}", e.OriginalSource, sender);
        }
        private void root_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            IsMouseOver = false;
            if (CanCloseOnLeave()) {
                _popTimer.Change(CloseDelay, System.Threading.Timeout.Infinite);
            }
            Debug.WriteLine("Mouse Leave={0}, {1}", e.OriginalSource, sender);
        }
        private bool CanCloseOnLeave()
        {
            Debug.WriteLine("CanCloseOnLeave: CloseOnHover={0}, HasFocus={1}, HoldOpenWhenFocused={2}, Eval={3}", _closeOnHover, HasFocus, HoldOpenWhenFocused, (_closeOnHover && ((HasFocus && HoldOpenWhenFocused) || (!HoldOpenWhenFocused))));
            if (_closeOnHover && ((HasFocus && HoldOpenWhenFocused) || (!HoldOpenWhenFocused)))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        private void root_GotFocus(object sender, System.Windows.RoutedEventArgs e)
        {
            HasFocus = true;
            if (e.OriginalSource is ContentControl)
            {
                if ((e.OriginalSource is ContentControl) && !(e.OriginalSource is AutoExpandOver) && (((ContentControl)e.OriginalSource).Name == "ContentArea1") && ChildIsValid(PopupChild))
                {
                    ClosePopup(false);
                }
            }
            Debug.WriteLine("Got Focus={0}, IsAutoExpandOver={1}, IsContentControl={2}", e.OriginalSource, (e.OriginalSource is AutoExpandOver), (e.OriginalSource is ContentControl));
        }
        private void root_LostFocus(object sender, System.Windows.RoutedEventArgs e)
        {
            if (!IsRelated(System.Windows.Input.FocusManager.GetFocusedElement(), RootElement))
            {
                HasFocus = false;
            }
            Debug.WriteLine("Lost Focus={0}", e.OriginalSource);
        }
        private void PopupTimer_Elapsed(object state)
        {
            if (RootElement != null)
            {
                if (_closeOnHover && !IsMouseOver)
                {
                    RootElement.Dispatcher.BeginInvoke(() => ClosePopup(true));
                }
                else if (_openOnHover && IsMouseOver)
                {
                    RootElement.Dispatcher.BeginInvoke(() => OpenPopup(true));
                }
            }
        }

        /// <summary>
        /// Identifies if the child is a child of parent (or is the parent).
        /// </summary>
        /// <param name="child">control which has gained focus</param>
        /// <param name="parent">container you are monitoring</param>
        /// <returns></returns>
        /// <remarks>Can be used to determine if a bubbled lost focus event resulted in focusing another child of the parent.
        /// ie. IsRelated(System.Windows.Input.FocusManager.GetFocusedElement(), StackContainer)</remarks>
        private bool IsRelated(object child, object parent)
        {
            return ((child == parent) || ((child is FrameworkElement) && (IsRelated(((FrameworkElement)child).Parent, parent))));
        }
        
    #endregion

    #region Public Methods

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            RootElement = GetTemplateChild(RootElementPart) as Panel;
            HeaderElement = GetTemplateChild(HeaderElementPart) as FrameworkElement;
            ContentElement = GetTemplateChild(ContentElementPart) as FrameworkElement;
            PopupElement = GetTemplateChild(PopupElementPart) as Popup;
        }

        /// <summary>
        /// Closes the popup and gives a chance to validate it if can close
        /// </summary>
        /// <param name="validate">Should we check if we can close</param>
        /// <returns>If it was sucessfull in closing</returns>
        public bool ClosePopup(bool validate)
        {
            if (PopupEnabled)
            {
                if ((CanCloseOnLeave() && !IsMouseOver) || (validate == false))
                {
                    OnPopupClosing();

                    VisualStateManager.GoToState(this, CollapsedState, true);
                    if (PopupChildCanvas != null)
                    {
                        PopupChildCanvas.Children.Remove(OutsidePopupCanvas);
                        PopupChildCanvas.Children.Remove(PopupChild);
                        PopupElement.Child = PopupChild;
                        OutsidePopupCanvas.MouseLeftButtonDown -= new MouseButtonEventHandler(OutsidePopupCanvas_MouseLeftButtonDown);
                        OutsidePopupCanvas = null;
                        PopupChildCanvas = null;
                    }

                    OnPopupClosed();
                } 
            }

            return true;
        }

        public void OpenPopup(bool validate)
        {
            if (PopupEnabled  && !PopupElement.IsOpen)
            {
                if ((HasFocus || IsMouseOver) || (validate == false))
                {
                    PopupChild = PopupElement.Child as FrameworkElement;

                    OnPopupOpening();

                    VisualStateManager.GoToState(this, ExpandedState, true);
                    if (MakeModal)
                    {
                        if (PopupChildCanvas == null)
                        {
                            MatrixTransform mt = null;
                            try
                            {
                                mt = RootElement.TransformToVisual(null) as MatrixTransform;
                            }
                            catch
                            {
                                Debug.WriteLine("Failed mt Transform");
                            }
                            if (mt == null)
                            {
                                return;
                            }

                            PopupChildCanvas = new Canvas();
                            PopupElement.Child = PopupChildCanvas;
                            OutsidePopupCanvas = new Canvas();
                            OutsidePopupCanvas.Width = Application.Current.Host.Content.ActualWidth;
                            OutsidePopupCanvas.Height = Application.Current.Host.Content.ActualHeight;
                            Matrix transformToRootMatrix = mt.Matrix;
                            Matrix newMatrix;
                            transformToRootMatrix.Invert(out newMatrix);
                            mt.Matrix = newMatrix;

                            OutsidePopupCanvas.RenderTransform = mt;
                            OutsidePopupCanvas.Background = new SolidColorBrush(Colors.Transparent);
                            OutsidePopupCanvas.MouseLeftButtonDown += new MouseButtonEventHandler(OutsidePopupCanvas_MouseLeftButtonDown);
                            OutsidePopupCanvas.MouseEnter += new MouseEventHandler(root_MouseLeave);
                            OutsidePopupCanvas.MouseLeave += new MouseEventHandler(root_MouseEnter);

                            PopupChildCanvas.Children.Add(OutsidePopupCanvas);
                            PopupChildCanvas.Children.Add(PopupChild);
                            try
                            {
                                PopupChild.MouseLeftButtonDown += new MouseButtonEventHandler(ContentElement_MouseLeftButtonDown);
                            }
                            catch { }
                        }
                    }

                    OnPopupOpened();
                }
            }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public AutoExpandOver() : base()
        {
            DefaultStyleKey = typeof(AutoExpandOver);
            _popTimer = new System.Threading.Timer(new System.Threading.TimerCallback(PopupTimer_Elapsed), null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
            this.GotFocus += new RoutedEventHandler(root_GotFocus);
            this.LostFocus += new RoutedEventHandler(root_LostFocus);
            this.MouseLeftButtonDown += new MouseButtonEventHandler(AutoExpandOver_MouseLeftButtonDown);
        }
        #endregion

    }
}
